#include "Operations.h"

bool Operations::hasRun = false;
//struct DisplayText* Operations::displayText = new DisplayText;//n
//OutputDisplay Operations::outputdisplay;//n

Month MyTime::gotoNextMonth() {
	const CommonDataType::Byte currentDay = timeData[(pos_int)TimeType::DAY];
	do {
		MyTime * myself = (MyTime*)this;
		(*myself)++;
	}
	while (currentDay != timeData[(pos_int)TimeType::DAY]);

	return (Month)timeData[(pos_int)TimeType::MONTH];
}

bool MyTime::operator == (const MyTime& rightOperand) {
	if (getTime (TimeType::YEAR) == rightOperand.getTime (TimeType::YEAR) &&
		getTime (TimeType::MONTH) == rightOperand.getTime (TimeType::MONTH) &&
		getTime (TimeType::DAY) == rightOperand.getTime (TimeType::DAY)) {
			return true;
	}
	else {
		return false;
	}
}

MyTime::MyTime (const Time& theOtherTime) {
	reset();
	for (pos_int i = FIRST_MEMBER; i < TIME_MAX; i++ ) {
		timeData[i] = theOtherTime.getTime((TimeType)i);
	}
}

void MyTime::operator = (const Time& rightOperand) {
	for (pos_int i = FIRST_MEMBER; i < TIME_MAX; i++ ) {
		timeData[i] = rightOperand.getTime((TimeType)i);
	}
}

Operations::Operations() {
	_availableData.reset();
	readFromStorage();
}

Operations& Operations::get() {
	static Operations lastOperation; 

	if (hasRun == false) {

		hasRun = true;
	}

	return lastOperation;
}

Operations::~Operations() {
	hasRun = false;
}

/*
void Operations::getDisplayTextPointer(struct DisplayText* displayTextPointer){//n
	DisplayText* temp = displayText;
	displayText = displayTextPointer;
	delete temp;
}
*/

void Operations::readFromStorage(){
	EventStorage* ptr = &EventStorage::get();
	_eventList = ptr->getEventVectorPtr();
}

/*
Description: 
	To load operations based on the user's command
Preconditions: 
	1. UserCommand class check if the command if of the correct format
	2. UserCommand class pass the OperationType to class Operation
Postconditions:
	1. call respective operation
*/
void Operations::executeCommand (UserCommand& aCommand) {	
	OperationType operation = aCommand.getOperationType();
	
	Event const * anEvent;
	vector<pos_int> const* IDs;
	pos_int const* ID;
	SearchStruct const * searchPtr;

	switch (operation) {
	case OperationType::UNCLEAR:
		failProgram (ERR_Operations::unclear_command);
		break;

	case OperationType::ADD: 
		//ptr->push(aCommand);
		anEvent = (Event const *)aCommand.getInput();
		addEvent(*anEvent);
		//outputdisplay.showAdded(displayText,anEvent);//n
		break;

	case OperationType::DELETE_BY_NUMBER:
		IDs = (vector<pos_int> const *)aCommand.getInput();
		if (IDs->size() == 0 ) {
			failProgram (ERR_Operations::passing_empty_delete_array);
		}
		deleteByNumber(*IDs);	
		break;

	case OperationType::MODIFY:
        //ID = (vector<pos_int> const *)aCommand.getInput();
        //if (ID->size() == 0 ) {
		//	failProgram (ERR_Operations::passing_empty_delete_array);     ///////////////////
		//}
		//modifyEvent(*ID);
		break;

	case OperationType::SEARCH:
		searchPtr = (SearchStruct const *)aCommand.getInput();
		performGeneralSearch (*searchPtr);
		break;

	case OperationType::SORT:
		break;

	case OperationType::BLOCK:
		break;

	case OperationType::MARK:
		ID = (pos_int*)aCommand.getInput();
		markEvent(*ID);
		break;
		
	case OperationType::UNDO:
		//executeUndo();
		break;

	case OperationType::HELP:
		break;

	case OperationType::CONFIG:
		break;

	case OperationType::SAVEFILE:
		saveToFile();
		break;

	case OperationType::LOADFILE:
		loadFromFile();
		break;

	case OperationType::EXIT:
		saveToFile();
		break;

	default:
		failProgram (ERR_Operations::unknown_command);
		break;
	}
}

//void Operations::executeUndo(UserCommand& aCommand){

//}

void Operations::performGeneralSearch (const SearchStruct& searchInfo){
	vector<pos_int> availabelID;

	for (auto i:searchInfo.filters) {
		if (i == SearchFilter::ANYTHING){
			for (pos_int j = 0; j < _eventList->size(); j++) {
				availabelID.push_back (j);
			}
			break;
		}

		else{
			bool messageCheck = false;
			bool tagsCheck = false;
			bool timeCheck = false;
			if (searchInfo.messages.size() != 0)
				messageCheck = true;
			if (searchInfo.tags.size() != 0)
				tagsCheck = true;
			if (searchInfo.beginTime.isEmpty() == false)
				timeCheck = true;

			vector<SearchRelevance> foundEvents;
			
			for (pos_int j = 0; j< _eventList->size(); j++){
				pos_int match = 0; 
				pos_int distance = 0;
				
				if (messageCheck && (!tagsCheck) && (!timeCheck)){
					checkDetailsMet((*_eventList)[j], searchInfo.messages, match, distance);
					if (match > 0){
						setFoundEvents(foundEvents, j, match, distance);
					}
				}
				if ((!messageCheck) && tagsCheck && (!timeCheck)){
					checkTagMet((*_eventList)[j], searchInfo.tags, match, distance);
					if (match > 0){
						setFoundEvents(foundEvents, j, match, distance);
					}
				}
				if ((!messageCheck) && (!tagsCheck) && timeCheck){
					checkTimeMet((*_eventList)[j], searchInfo.beginTime,searchInfo.endTime, match, distance);
					if (match > 0){
						setFoundEvents(foundEvents, j, match, distance);
					}
				}
				if (messageCheck && tagsCheck && (!timeCheck)){
					checkDetailsMet((*_eventList)[j], searchInfo.messages, match, distance);
					checkTagMet((*_eventList)[j], searchInfo.tags, match, distance);
					if (match > 0){
						setFoundEvents(foundEvents, j, match, distance);
					}
				}
				if (messageCheck && (!tagsCheck) && timeCheck){
					checkDetailsMet((*_eventList)[j], searchInfo.messages, match, distance);
					checkTimeMet((*_eventList)[j], searchInfo.beginTime,searchInfo.endTime, match, distance);
					if (match > 0){
						setFoundEvents(foundEvents, j, match, distance);
					}
				}
				if ((!messageCheck) && tagsCheck && timeCheck){
					checkTagMet((*_eventList)[j], searchInfo.tags, match, distance);
					checkTimeMet((*_eventList)[j], searchInfo.beginTime,searchInfo.endTime, match, distance);
					if (match > 0){
						setFoundEvents(foundEvents, j, match, distance);
					}
				}
				if (messageCheck && tagsCheck && timeCheck){
					checkDetailsMet((*_eventList)[j], searchInfo.messages, match, distance);
					checkTagMet((*_eventList)[j], searchInfo.tags, match, distance);
					checkTimeMet((*_eventList)[j], searchInfo.beginTime,searchInfo.endTime, match, distance);
					if (match > 0){
						setFoundEvents(foundEvents, j, match, distance);
					}
				}

				if (i != SearchFilter::NORMAL){
					if (!(isFiltersMet((*_eventList)[j], searchInfo.filters))){
						if ((messageCheck || tagsCheck || timeCheck) && (foundEvents.size() != 0)){
							foundEvents.pop_back();
						}
					}
					else{
						if ((!messageCheck) && (!tagsCheck) && (!timeCheck)){
							match++;
							setFoundEvents(foundEvents, j, match, distance);
						}
					}
				}
			}
			sort(foundEvents.begin(), foundEvents.end(), compareByRelevance);
			for (pos_int i=0; i<foundEvents.size(); i++){
				availabelID.push_back(foundEvents[i].storageIndex);
			}
		}	
	}
	_availableData.setMapping (availabelID);
	ReadOutputDisplay ptr = &OutputDisplay::get();
	ptr->displayEvents (availabelID);
}



void Operations::addEvent(Event anEvent){
	_eventList->push_back(anEvent);
	OutputDisplay::get().showAdded (anEvent.getDetails());
	//printOnScreen(); 
}

void Operations::deleteByNumber(const vector<pos_int>& IDs){ 
	vector<pos_int> deletedIDs;
	vector<pos_int> notDeletedIDs;
	pos_int nDeleted = 0;
	for (auto i:IDs) {
		int storageID = _availableData.getStorageIndex (i);
		if (storageID >= 0) {
			deletedIDs.push_back (i);
			_availableData.deleteMapping (i);
			//ActionList* ptr = &ActionList::get();
			
			_eventList->erase (_eventList->begin() + storageID);
			nDeleted++;
		}
		else {
			notDeletedIDs.push_back(i);
		}
	}
	if (nDeleted == 0) {
		deletedIDs.clear();
		deletedIDs.shrink_to_fit();
		notDeletedIDs.clear();
		notDeletedIDs.shrink_to_fit();
	}
	OutputDisplay::get().showdeleted(deletedIDs, notDeletedIDs);
}


void Operations::deleteByEvent(Event anEvent){
	//for (pos_int i=0; i<(IDs).size(); i++){
	//	pos_int index = (IDs)[i] - 1; 
		//Event eventDeleted = _eventList->at(index);
		//_eventList->erase(_eventList->begin()+index);
	//}
	//displayEvent();
}


void Operations::modifyEvent(pos_int ID){                     //
    // pos_int _IDToModify = ;                                 //
    // pos_int _IDModified = ;                                   //
    // OutputDisplay::get().getToModify(_IDToModify);   // later need to show it in another place, n
     //
    // OutputDisplay::get().showModified(_IDModified);
}



void Operations::markEvent(pos_int ID) {
	/*Event* anEvent = _eventList->at(index);
	if (!(anEvent->getIsDone())){
		addToHistoryEvents(anEvent);
		anEvent->setIsDone(true);
	}
	else{
		//error message
	}*/
}


void Operations::undoCommand () {
	//pos_int currentPos = _historyCommands->size() - _numberOfUndo - 1; 

	/*OperationType lastOperation = _historyCommands.back();
	_historyCommands.pop_back();
	pos_int numberOfEvents;
	Event* anEvent;
	
	switch (lastOperation) {

	case OperationType::ADD:
		_eventList->pop_back();
		_historyEvents->pop_back();
		break;

	case OperationType::DELETE_:
		numberOfEvents = _historyIDsPerCommand.back();
		for (pos_int i = 0; i < numberOfEvents; i++){
			Event* anEvent = _historyEvents->back();
			pos_int index = _historyIDs.back() - 1;
			_eventList->insert(_eventList->begin()+index, anEvent);
			_historyEvents->pop_back();
			_historyIDs.pop_back();
		}
		break;

	case OperationType::MARK:
		anEvent = _historyEvents->back();
		anEvent->setIsDone(false);
		break;

	case OperationType::MODIFY:

		break;
	

	case OperationType::BLOCK:
		break;
	}*/
}

void Operations::saveToFile(){
	EventStorage* ptr = &EventStorage::get();
	ptr->saveFile();
}

void Operations::loadFromFile(){
	EventStorage* ptr = &EventStorage::get();
	ptr->readFile();
}

